Support codex and gemini CLIs as architects (PIR #929)#1059
Support codex and gemini CLIs as architects (PIR #929)#1059mohidmakhdoomi wants to merge 27 commits into
Conversation
…dex/gemini architects Fixes a latent crash-loop where a non-Claude architect/builder + a stale Claude .jsonl built an invalid `<cmd> --resume <claude-uuid>` invocation and shellper restart-looped to death. Session resume is now gated on the configured HarnessProvider: - harness.ts: add optional buildResume (bundled discovery + Node-argv + shell-escaped script fragment) and getArchitectFiles. Only CLAUDE_HARNESS implements buildResume; GEMINI_HARNESS writes .gemini/settings.json → AGENTS.md so gemini launches with project context. - tower-instances.ts: architect resume gated on getArchitectHarness().buildResume, preserving the safeToResume sibling guard; getArchitectFiles written if-absent. - spawn.ts / spawn-worktree.ts: discoverResumeSession takes the builder harness and returns the bundled resume object; the launch script consumes the pre-escaped scriptFragment instead of re-deriving the flag. - doctor.ts / arch.md: affirm codex/gemini architect support; document that conversation resume is Claude-main-only and selection is config-driven. Tests: codex/gemini return no --resume with a stale jsonl (architect + builder); claude still resumes; gemini settings.json write-if-missing + no-clobber. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…are harness + centralized context files BLOCKER B: getArchitectHarness/getBuilderHarness now auto-detect the harness from the override-aware resolved command (getResolvedCommands honoring TOWER_ARCHITECT_CMD / --architect-cmd / --builder-cmd), so a non-claude command override no longer resolves the claude harness and re-arms the resume crash-loop. BLOCKER A: centralize getArchitectFiles writing into the shared buildArchitectArgs (exported writeArchitectContextFiles) and route the no-Tower architect command through it, so sibling / no-Tower / reconnect gemini launches all get .gemini/settings.json — not just first-activation. Nits: gate the resume-skip WARN on buildResume support; distinguish "harness does not support resume" in discoverResumeSession; gitignore .gemini/settings.json (repo + managed adopter list). Adds config.test.ts (override-aware resolution) and tower-utils.test.ts (writeArchitectContextFiles) regression coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tmatter rebuttal Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cluesmith#1059, no self-merge) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Architect Integration RE-REVIEW — APPROVE (with one documented caveat)3-way CMAP re-run on the post-rework diff ( Verdicts: gemini Verified fixed
codex's finding — verified, dispositioned as documented + follow-up (not blocking)
Tracked for hardening in #1062; an in-repo known-limitation note is being added to this PR's docs. Net: ready to merge. (Contribution PR — merge is a cluesmith maintainer's action.) Architect integration re-review |
…ult to claude harness (cluesmith#1062) 3-way re-review APPROVE-with-caveat. Codex's verified finding: resolveHarness defaults an unrecognized override command (e.g. TOWER_ARCHITECT_CMD=bash) to CLAUDE_HARNESS when no explicit shell.architectHarness/builderHarness is set, so it can still attempt resume against a stale claude jsonl. Pre-existing and narrow (not a cluesmith#929 regression). Documented in arch.md + review; follow-up cluesmith#1062. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…self-merged (awaiting maintainer on cluesmith#1059) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PIR Review: Support
codexandgeminiCLIs as architectsFixes #929
Summary
Brings the OpenAI
codexand GooglegeminiCLIs to parity withclaudeas Codev architects, selectable via.codev/config.json(shell.architect/shell.architectHarness). The core fix routes session-discovery +--resumeargument construction behind a new optionalHarnessProvider.buildResumecapability, eliminating a latent crash-loop where a non-Claude architect (or resumed builder) with any stale Claude.jsonlbuilt an invalid<cmd> --resume <claude-uuid>invocation and shellper restart-looped to death. Gemini additionally gets a write-if-absent.gemini/settings.jsonso it launches with project context (AGENTS.md), anddoctornow affirms codex/gemini architect support.Files Changed
Resume seam + gemini context (initial implementation):
packages/codev/src/agent-farm/utils/harness.ts—buildResume?+getArchitectFiles?on the interface;CLAUDE_HARNESS.buildResume(delegates tofindLatestSessionId);GEMINI_HARNESS.getArchitectFiles(.gemini/settings.json)packages/codev/src/agent-farm/servers/tower-instances.ts— architect resume gated ongetArchitectHarness(...).buildResume?.(); fresh path delegates context-file writing tobuildArchitectArgspackages/codev/src/agent-farm/commands/spawn.ts—discoverResumeSessiontakes the builder harness, returns the bundled resume object; both call sites passgetBuilderHarness(...); distinct "harness does not support resume" log (nit 2)packages/codev/src/agent-farm/commands/spawn-worktree.ts—startBuilderSession'sresumeSessionId?: string→resume?: { sessionId, scriptFragment }; script emits the pre-escaped fragmentpackages/codev/src/commands/doctor.ts— affirm codex/gemini architect support; single resolved-harness checkIntegration-review fixes (architect punch list — 2 blockers + 3 nits):
packages/codev/src/agent-farm/utils/config.ts— BLOCKER B:getArchitectHarness/getBuilderHarnessnow auto-detect from the override-aware command (getResolvedCommands);getResolvedCommands.architecthonorsTOWER_ARCHITECT_CMDpackages/codev/src/agent-farm/servers/tower-utils.ts— BLOCKER A: exportedwriteArchitectContextFiles, called from the sharedbuildArchitectArgsso every launch path writes gemini's manifest; nit 1: (see tower-instances) WARN gated on resume supportpackages/codev/src/agent-farm/commands/architect.ts— BLOCKER A: no-Tower path refactored to callbuildArchitectArgs(was duplicating injection).gitignore,packages/codev/src/lib/gitignore.ts— nit 3: ignore.gemini/settings.json(repo + managed adopter list)Tests:
packages/codev/src/agent-farm/__tests__/tower-instances.test.ts— architect resume-skip regression guard + geminigetArchitectFileswrite-if-missing/no-clobberpackages/codev/src/agent-farm/__tests__/spawn-worktree.test.ts— builder resume script uses escapedscriptFragment; codex/gemini → fresh scriptpackages/codev/src/agent-farm/__tests__/discover-resume-session.test.ts— harness-arg threading; codex/gemini null-return + claude bundled-object casespackages/codev/src/agent-farm/__tests__/config.test.ts— BLOCKER B regression: override-aware harness resolution (TOWER_ARCHITECT_CMD / --architect-cmd / --builder-cmd → non-claude harness, nobuildResume)packages/codev/src/agent-farm/__tests__/tower-utils.test.ts— BLOCKER A regression:writeArchitectContextFilesgemini write + no-clobber + claude no-oppackages/codev/src/agent-farm/__tests__/af-architect.test.ts— updated mocks for thebuildArchitectArgsdelegationpackages/codev/src/__tests__/gitignore.test.ts,update.test.ts— managed-entry expectations include.gemini/settings.jsonDocs / artifacts:
codev/resources/arch.md— supported-architect-harnesses + Claude-only-resume + override-aware resolution +getArchitectFilescentralizationcodev/plans/...,codev/reviews/...,codev/state/pir-929_thread.md,codev/resources/lessons-learned.md,codev/projects/929-*/status.yamlCommits
69cf20de[PIR 929][Phase: implement] feat: harness-gated session resume for codex/gemini architects53374f30[PIR Supportcodexas an architect #929] Plan revised — address architect feedback (5 issues)fdddc7e2[PIR Supportcodexas an architect #929] Plan draftTest Results
pnpm build: ✓ pass (clean TS types)pnpm vitest run(full suite): ✓ 3338 passed, 48 skipped, 0 failuresafx sendmultiline/interrupt/streaming, reconnect, affinity, builder--resume, dashboard scrollback) was exercised by the human at thedev-approvalgate against the running worktree — the reason PIR was chosen over AIR/BUGFIX. The architect's subsequent integration review caught the two override/path-dependency blockers below (fixed + regression-tested in this PR).Architecture Updates
COLD (
codev/resources/arch.md) — updated in the implementation commit. Added a "Supported Architect Harnesses & Conversation Resume (#929)" subsection documenting: (1) claude/codex/gemini are all supported architects selected via.codev/config.json(notTOWER_ARCHITECT_CMD/--architect-cmd); (2) gemini's.gemini/settings.json→AGENTS.mdcontext manifest; (3) conversation resume is Claude-main-only viaHarnessProvider.buildResume, and the crash-loop it fixes. Also updated the role-injection step to point at theHarnessProviderper-CLI flags rather than the claude-only--append-system-prompt.No HOT (
arch-critical.md) change: the harness abstraction and its provider-method-extension pattern are already implied by the existing "Forge concept commands abstract the VCS provider — add a dedicated concept" entry's spirit; this PR extends an existing abstraction (Spec 591) rather than introducing a new always-on invariant, so a cold-tier reference detail is the correct routing.Lessons Learned Updates
COLD (
codev/resources/lessons-learned.md, Architecture section) — added one lesson: when abstracting per-CLI behavior behind a provider, every call site that builds a CLI invocation must route through the provider — including resume/restart paths, not just the obvious fresh-launch path. The resume seam was the one path Spec 591 left harness-blind, and it only crash-loops on the--resumebranch (fresh launches were already correct), which is why "builders already prove the path" didn't cover it.No HOT (
lessons-critical.md) change: the existing "Single source of truth beats distributed state" and "Model permissions as roles/capabilities, not booleans" hot entries already carry the general displace-when-full discipline; this is a spec-narrow recipe (audit all invocation seams when extending a provider) better suited to the cold archive.Things to Look At During PR Review
buildResumebundling decision (harness.ts): one method returns both the Node-argvargs(for thespawn()architect site) and a shell-escapedscriptFragment(for the builder bash generator), mirroringbuildRoleInjection/buildScriptRoleInjection. This deliberately avoids a second independently-optional method (which would force a!non-null assertion) and avoids.join(' ')-ing a raw argv into bash (word-split/quoting bug). Session ids are bare UUIDs today, so the escaping is belt-and-suspenders — kept for correct-by-construction consistency with the existing script-injection methods.safeToResumeinteraction (tower-instances.ts): the new harness gate composes with the pre-existing sibling-collision guard (safeToResume, Multi-architect conversation resume: disambiguate via per-architect session UUID #832) — resume happens only when both the harness implementsbuildResumeand no persisted siblings exist. Confirm the ordering reads correctly.getArchitectFileswrite-if-absent (tower-instances.ts): writes.gemini/settings.jsononly when the target path doesn't exist, so a user's existing file is never clobbered. Test covers both the write and the no-clobber path.getResolvedCommands.architectis nowcliOverrides.architect || TOWER_ARCHITECT_CMD || config. Within the Tower processcliOverridesis empty (it's set in the spawningafxprocess, not the long-lived server), so this matches the launch site'sTOWER_ARCHITECT_CMD || config. An explicitshell.architectHarness/shell.builderHarnessstill wins over auto-detection by design — so a deliberately contradictoryarchitectHarness: claude+ gemini command is the user's call, not auto-resolved. Worth a sanity check that this precedence reads as intended.getArchitectFilescentralization (BLOCKER A fix): moved the inline write out oflaunchInstanceinto the sharedbuildArchitectArgs(writeArchitectContextFiles), and refactored the no-Towerarchitect.tsto callbuildArchitectArgsinstead of duplicating role injection. Confirm the no-Tower path's arg shape is unchanged (covered byaf-architect.test.ts) and that the claude resume path — the one path that does not callbuildArchitectArgs— correctly needs no context files (claude has nogetArchitectFiles).resolveHarness(harness.ts) still falls through toCLAUDE_HARNESSfor an unrecognized override command (e.g.TOWER_ARCHITECT_CMD=bashor a wrapper script) when no explicitshell.architectHarness/shell.builderHarnessis set — so it can still attempt<cmd> --resume <uuid>against a stale Claude.jsonl. This is pre-existing, narrow, and separable (not a Supportcodexas an architect #929 regression — Supportcodexas an architect #929 strictly improved the recognized cases), dispositioned as document + follow-up. Mitigation: set an explicit harness for unrecognized launcher commands. Documented inarch.md's Supportcodexas an architect #929 subsection; tracked in Harden harness resolution: unrecognized override command defaults to claude harness (residual --resume crash-loop) #1062.Consultation Findings & Dispositions
The PR diff was reviewed by a 2-way advisory CMAP pass after the integration-review fixes landed:
codev/plans/929-...mdhas no YAML approval frontmatter (approved:/validated:)."plan-approvalgate (recorded incodev/projects/929-*/status.yaml:plan-approval: approved 2026-05-31) — the gate is the approval record for builder-authored plans. None of the existing merged plans incodev/plans/carry that frontmatter either (verified:0001–0009start with a#heading /## Metadata, noapproved:key). Adding a self-authoredapproved:/validated:block would fabricate an approval record that porch already holds authoritatively. No code change warranted. Escalated to the human at theprgate per PIR single-pass policy.How to Test Locally
For reviewers pulling the branch:
pir-929→ Review Diffafx dev pir-929shell.architectaccordingly):afx workspace startmain architect launches with a stale Claude.jsonlpresent in~/.claude/projects/<encoded-cwd>/— must NOT crash-loop, and no--resumein the launched command (primary regression target)afx architect(no-Tower) +afx workspace add-architectlaunch with role injected.gemini/settings.jsonwritten withcontext.fileName: "AGENTS.md"(pre-existing one untouched)afx sendsingle-line / multi-line (>3 lines) /--interrupt/ while streamingafx spawn <id> --resumeon a non-Claude builder → fresh launch + resume notice, inspect.builder-start.shfor no--resume <claude-id>